Managing the Desktop "Oh, London is a fine town, a very famous city where all the streets are paved with gold and all the maidens pretty" George Coleman the younger, 1762-1836 The Heir at Law Introduction A desktop application typically has a pull-down menu, a status bar, and a main area (in between) where windows are displayed. One of the best known desktop applications (for us programmer types) is Borland's integrated development environment for C and Pascal. The unit GOLDDESK provides a set of elegant tools to make it easy for you to create a sophisticated desktop application. We don't need no stinkin' Turbo Vision!! (Just kidding David.) Figure 12.1 A Typical Desktop Ingredients of a Desktop A full desktop includes a pull-down menu, a status bar and one or more windows. Gold also allows you to define a partial desktop which has either a menu or a status bar. Having built the individual desktop components, the desktop is constructed using the DeskAssignxxx functions. The Pull-down Menu The desktop utilizes a standard Gold pull-down menu. If you are not familiar with these menus, read Chapter 11. Having constructed a menu, it can be added to the desktop with the procedure DeskAssignMainMenu. This procedure is passed the MenuBar variable which defines the menu. For example: DeskAssignMainMenu(MainMenu); That's all there is to it. If, for some strange reason, you want to continue using the desktop, but want the menu removed, just call the procedure DeskRemoveMainMenu. The Status Bar At status bar is a single row of options positioned on the bottom line of the display. It's like a menu bar without any popups. The code used to create a status bar is exactly the same as the code to create a menu bar. If you want to add a status bar to the desktop, create a variable of type MenuBar, initialize the variable with InitBar, and use the procedure DeskAssignStatusBar to add the status bar to the desktop. The following code fragment illustrates this approach: var StatusBar: bar; begin InitBar(StatusBar); BarAddItem(StatusBar,'~F1~ Help',1001,315,315,'',nil); BarAddItem(StatusBar,'~Alt+X~ Exit the demo',999,301, 301,'',nil); DeskAssignStatusBar(Statusbar); ... end; The status bar can be removed by calling the procedure DeskRemoveStatusBar. The Background Every desktop has a background; this is the main area of the screen which extends from the pull-down menu to the status bar. Normally, this area is filled with a single character to provide a plain wallpaper. You can change the default appearance of the background by assigning a different character to the variable DeskVars.BackChar. Also, you can change the background color by using the GoldSetColor procedure and assigning a new value to the DeskBack element. For example: DeskVars.BackChar := chr(179) GoldSetColor(DeskBack,WhiteonGreen); Gold provides a way for you to create more sophisticated backgrounds. All you have to do is create a procedure following some specific rules, and then call the DeskAssignPaintProc procedure to instruct Gold to call your procedure every time the background needs to be repainted. For a procedure to be eligible as a paint procedure it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with no parameters. The following procedure declaration follows these rules: {$F+} procedure CustomBgnd; begin {some code} end; {CustomBgnd} {$F-} The following procedure is then called to instruct Gold to call your procedure every time the desktop needs painting: DeskAssignPaintProc (Hook:KeyPressedHook); Instructs Gold to call the specified procedure every time Gold needs to draw the background. If, subsequently, you want to remove the custom paint procedure, execute the command DeskRemovePaintProc. Run the demo program DEMDESK7.PAS to see a custom paint procedure in action. Responding to User Input A desktop is simply a standard way to garner user input. The user can select items from the menu, click on the status, or press hotkeys. The application must then respond to this input and act accordingly. So much for the theory of desktops. Gold takes care of managing the user input. You don't need to explicitly invoke the menu or status bar; Gold takes care of it for you. If the keystroke or mouse activity is directed towards the pull-down menu, the keystroke will be passed to the menu for processing. Similarly, status bar-related keystrokes will be passed to the status bar for processing. If the user clicks on the top window (if there is a window visible), the keystroke is passed to the window for processing. Finally, if the user clicks on a window without focus, the window is moved to the top, i.e. given focus. Assigning an Action Procedure Every item in the menu and status bar has an ID -- you assigned the ID as one of the arguments in BarAddItem and PopAddItem. When the user selects an item from the desktop, Gold passes the ID of the selected item to a user-defined action procedure. All you have to do is create a procedure following some specific rules, and then call the DeskAssignActionProc procedure to instruct Gold to call your procedure every time the user makes a selection. For a procedure to be eligible as an action procedure it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with two passed parameters. The first parameter is an integer and a variable parameter of type DAction. The following procedure declaration follows these rules: {$F+} procedure MyActionProc(Choice:integer; var Action:DAction); {} begin case Choice of ..... end; end; { MyActionProc } {$F-} The following procedure is then called to instruct Gold to call your procedure after each desktop input: DeskAssignActionProc(Aproc:DeskActionProc); Instructs Gold to call the specified procedure every time the user makes a status bar or menu selection from the desktop. Writing the Action procedure The first parameter passed to the action procedure is the ID of the item selected from the menu or status bar. The body of the action procedure usually takes the form of a case statement which branches to another part of the program based on the user selection. The second parameter passed to the action procedure is a variable parameter of type DAction, which is an enumerated type declared in GOLDDESK as follows: DAction = (DFinished, Descaped, DRepaint, DNone, DCloseTop, DCloseAll,DStop1..DStop99); By updating this variable with a value (other than DNone) you can instruct Gold to take some form of action. For example, if you update the variable with a value of DRepaint, Gold will automatically repaint the menu, status bar, background, and every window. The following table elaborates on the actions taken buy Gold for each of the return values: DAction Value Meaning DFinished Ends the desktop session and returns the code DFinished. DEscaped Ends the desktop session and returns the code DEscaped. DRepaint Forces a repaint of the desktop and all the windows. DNone No action is taken - this is the default value when the action procedure doesn't modify the variable. DCloseTop Forces the top window to close. DCloseAll Forces all the windows to close. DStop1..DStop99 Ends the desktop session and returns the appropriate stop code. Starting a Desktop Session Having assigned the menu, status bar and action procedure, you are ready to launch the desktop and wait for user input. All you have to do is call the function DeskProcessInput. This function returns the DAction which caused the session to close (see the above table). A Simple Desktop Example Listed below is the entire source code for DEMDESK1.PAS which implements a minimalist desktop. Although the desktop doesn't have too many features, it does illustrate all the important principles of creating a desktop application. var MainMenu: Bar; SubMenu: PopUp; Action : dAction; procedure DefineSubMenu; {} begin InitPopUp(SubMenu); with SubMenu do begin PopUpAddItem(SubMenu,'~A~bout',101,65, 'Show version...ation',nil); PopUpAddItem(SubMenu,'E~x~it',999,88, '~Exit~ this little demo',nil); end; end; { DefineSubMenu } procedure DefineMainMenu; {} begin InitBar(MainMenu); BarAddItem(MainMenu,'~C~hoices',100,67,302, 'Select one of two menu options', @SubMenu); MainMenu.Style := 2; end; { DefineMainMenu } procedure DisposeMenus; {} begin DestroyBar(MainMenu); DestroyPopUp(SubMenu); end; { DisposeMenus } {$F+} procedure MyActionProc(Choice:integer;var Action:DAction); {} begin case Choice of 101: PromptOK(' The Gold Desktop ', '^Copyright 1995 TechnoJock Software, Inc.|' +'^All Rights Reserved'); 999: Action := DFinished; end; end; { MyActionProc } {$F-} begin DefineSubMenu; DefineMainMenu; DeskAssignMainMenu(MainMenu); DeskAssignActionProc(MyActionProc); MouseShow(true); CursorOff; Action := DeskProcessInput; CursorOn; MouseShow(false); DisposeMenus; end. Launching Desktop Windows A desktop is really a standard framework for launching windows or applets. Many of the Gold units include procedures which are exclusively designed for launching windows onto the desktop. In this section you will learn how to populate the desktop with windows. Understanding Modal and Non-Modal Windows A modal window is a window which disables all other aspects of the desktop and forces all input to the window. When a modal window is active, the user cannot switch to another window, nor select items from the menu or the status bar. Modal windows are ideal for error messages or dialogs which require immediate input, e.g. an open file dialog. Non-modal windows, on the other hand, can lose focus and form the heart of a true desktop application. Examples of non-modal windows include a file browser, calculator and calendar. Borland Pascal uses non-modal windows for the edit windows where the source code is managed. Launch-able Gold Components Many of the Gold units include primary procedures which begin with the word Run, e.g. RunCalculator, RunBrowse, etc. When the object can also be used on a desktop there is a related Launch function, e.g. LaunchCalculator, LaunchBrowse. Listed below is a summary of all the Gold procedures which can be used to launch non-modal windows onto the desktop. LaunchCalendar(StartDate:Dates;Tit:string): byte; Adds a month-at-a-glance window to the desktop. The arguments passed to this procedure are the same as for RunCalendar discussed in Chapter 7. The function returns the number (or handle) of the newly created window. LaunchCalculator(Tit:string): byte; Adds a push-button calculator to the desktop. The arguments passed to this procedure are the same as for RunCalculator discussed in Chapter 8. The function returns the number (or handle) of the newly created window. LaunchBrowse(var ListDetails: ListCfg;Tit:StrScreen; CloseProc:ListCloseProc):byte; Launches a browser for viewing the contents of an array or linked list. The arguments passed to this procedure are discussed in Chapter 14. The function returns the number (or handle) of the newly created window. LaunchBrowseFile(Fname:PathStr;Tit:StrScreen):byte; Launches a browser for viewing the contents of a file. The arguments passed to this procedure are the same as for RunBrowseFile discussed in Chapter 14. The function returns the number (or handle) of the newly created window. LaunchList(var ListDetails: ListCfg;Tit:StrScreen; CloseProc:ListCloseProc): byte; Adds a list window to the desktop. The arguments passed to this procedure are discussed in Chapter 14. The function returns the number (or handle) of the newly created window. LaunchGrid(var ListDetails: ListCfg;Tit:StrScreen):byte; Adds a list window to the desktop. The arguments passed to this procedure are the same as for RunGrid discussed in Chapter 14. The function returns the number (or handle) of the newly created window. LaunchMemo(var MemoDetails: MemoCfg;Tit:StrScreen; CloseProc:MemoCloseProc): byte; Adds a memo window to the desktop. The arguments passed to this procedure are discussed in Chapter 15. The function returns the number (or handle) of the newly created window. LaunchFormInit(X1,Y1,X2,Y2,style:byte;CloseProc:FormCloseProc):byte LaunchForm(StartField:byte); These functions are used to create non-modal IO forms, and are discussed in more detail in Chapter 16. The demo files DEMDESK2.PAS to DEMDESK5.PAS illustrate many of the above launch functions. Positioning New Windows Most desktop applications place a new window down and to the right of the (old) top window. Complications arise when the top window is located near the right or the bottom of the desktop where there is insufficient room for the new window. Gold offers the following two functions to help you calculate the dimensions of a new window: DeskNextWinDim(var X1,Y1,X2,Y2: shortint; MinWidth,MinDepth: longint); Updates the first four parameters with a set of coordinates which can be used to size a new window that is about to be added to the desktop. DeskNextWinCoords(var TLX,TLY: byte); Updates the passed parameters with the appropriate coordinates (of the top left of the window) which can be assigned to a new window that is about to be added to the desktop. Displaying Modal Windows There is nothing to stop you from using modal windows on the desktop. For example, you might use PromptStr to ask the user to input a password. In fact, most desktop applications use both modal and non-modal windows. In Borland Pascal, for example, a modal window is used to prompt for confirmation during a multiple search and replace operation. However, always remember that a modal window must be removed before the user can access the menu, status bar, or other windows. So use modal windows sparingly. Adding a Traditional "Window" menu If you like to conform to Microsoft Windows and DOS desktop applications standards, you should consider adding Window and Help items to the pull-down menu. The contents of the Help menu is application specific, but the Window menu often displays a standard set of items (see Figure 12.2). You create a window pop-up just like any other pop-up -- by adding items to a PopUpMenu, and by responding to the menu choices in the action procedure. The good news is that you do not have to write your own tiling and cascading procedures. GOLDDESK includes the following procedures to help with window management: Figure 12.2 A Standard Window Menu DeskCloseAllWindows: boolean; Closes all the windows on the desktop. If the function returns FALSE, one or more window could not be closed. This might occur, for example, when an IO form is closed but the value entered into a field is not valid. DeskNextWin; Changes focus to the next window in the list. DeskPrevWin; Changes focus to the previous window in the list. DeskStretchTopWin: boolean; Lets the user manually (without the mouse) stretch the window. A FALSE is returned if the window cannot be stretched. DeskZoomTopWin: boolean; Makes the top window the size of the desktop. A FALSE is returned if the window cannot be zoomed. DeskMoveTopWin: boolean; Lets the user manually (without the mouse) move the top window. A FALSE is returned if the window cannot be moved. DeskCascade; Arranges all the windows, cascading from the top left of the desktop. DeskTile: boolean; Tiles all the windows on the desktop. A FALSE is returned if the one or more windows cannot be tiled. DeskWinList; Displays a window listing all the files and allows the user to change focus. You can copy and paste the window related code from the demo file DEMDESK6.PAS into your own desktop application. Implementing a Hind Hook A desktop hind hook is a procedure which is called every time an input has been processed by the desktop. A hind hook can be used to change the grayed status of menu items based on the window with focus, for example. All you have to do is create a procedure following some specific rules, and call the DeskAssignHindHook procedure to instruct Gold to call your procedure every time some input has been processed. For a procedure to be eligible as a desktop hind hook, it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in Chapter 3 for further information. The procedure must be declared with no parameters. The following procedure declaration follows these rules: {$F+} procedure CustomHook; begin {some code} end; {CustomHook} {$F-} The following procedure is then called to instruct Gold to call your procedure every time the desktop needs painting: DeskAssignHindHook (Hook:KeyPressedHook); Instructs Gold to call the specified procedure every time Gold has processed an input to the desktop.. If, subsequently, you want to remove the hind hook procedure, execute the command DeskRemoveHindHook. Run the demo program DEMOVIEW.PAS to see a custom hind hook in action.